package main

import (
	"fmt"
	"runtime"
	"time"
)

func leakyFunction() {
	// 创建一个channel但不会被消费
	ch := make(chan int)

	go func() {
		val := <-ch // 这个goroutine将永远阻塞在这里，因为没有人会向这个channel发送数据
		fmt.Println("This will never be printed:", val)
	}()

	// 这里没有向ch发送数据，也没有关闭ch
	// 结果是上面创建的goroutine将永远阻塞，造成泄漏
}

func channelLeakInLoop() {
	// 在循环中不断创建泄漏的goroutine
	for i := 0; i < 10000; i++ {
		ch := make(chan int)

		// 每次循环都启动一个goroutine，但不提供退出机制
		go func() {
			<-ch // 阻塞在这里，永远不会退出
		}()

		// 循环继续，没有任何代码向ch发送数据或关闭它
	}
}

func withTimeoutLeakFixed() {
	for i := 0; i < 10000; i++ {
		ch := make(chan int)

		// 使用带有超时的上下文可以避免goroutine泄漏
		go func() {
			select {
			case <-ch:
				fmt.Println("Received value")
			case <-time.After(100 * time.Millisecond):
				// 超时后goroutine会退出
				return
			}
		}()

		// 循环继续，但现在goroutine有超时退出机制
	}
}

func contextCancelLeakFixed() {
	done := make(chan struct{})

	for i := 0; i < 10000; i++ {
		ch := make(chan int)

		go func() {
			select {
			case <-ch:
				fmt.Println("Received value")
			case <-done:
				// 当done通道关闭时，所有goroutine会退出
				return
			}
		}()
	}

	// 如果需要终止所有goroutine，只需关闭done通道
	close(done)
}

func main() {
	// 记录初始goroutine数量
	fmt.Println("初始goroutine数量:", runtime.NumGoroutine())

	// 演示泄漏
	fmt.Println("演示单个goroutine泄漏:")
	leakyFunction()
	runtime.GC() // 尝试强制GC，但泄漏的goroutine不会被回收
	fmt.Println("调用泄漏函数后goroutine数量:", runtime.NumGoroutine())

	// 等待一段时间，让GC有机会运行
	time.Sleep(time.Second)
	fmt.Println("1秒后goroutine数量:", runtime.NumGoroutine())

	// 大量goroutine泄漏
	fmt.Println("演示大量goroutine泄漏:")
	channelLeakInLoop()
	fmt.Println("循环泄漏后goroutine数量:", runtime.NumGoroutine())

	// 如何修复泄漏
	fmt.Println("演示使用超时避免泄漏:")
	withTimeoutLeakFixed()
	time.Sleep(200 * time.Millisecond) // 等待所有超时发生
	fmt.Println("使用超时机制后goroutine数量:", runtime.NumGoroutine())

	fmt.Println("演示使用取消信号避免泄漏:")
	contextCancelLeakFixed()
	time.Sleep(100 * time.Millisecond) // 给goroutine一些时间退出
	fmt.Println("使用取消信号后goroutine数量:", runtime.NumGoroutine())
}
